(function(){

	phnq.util =
	{
		Bitset: function()
		{
			this.nums = [];
		},
		
		URI: function(uri, params)
		{
			this.init(uri, params);
		},
		
		/*@
		*	phnq.util.appendCSS
		*	Appends CSS stylings to the current document.
		*	@param cssCode the CSS code that describes the stylings being added.
		*/
		appendCSS: function(cssCode)
		{
			var headElmnts = document.getElementsByTagName("head");
			if(headElmnts.length == 0)
				return;

			var headElmnt = headElmnts[0];

			var styleElmnt = document.createElement("style");
			styleElmnt.setAttribute("type", "text/css");
			if (styleElmnt.styleSheet)   // IE
			{
			    styleElmnt.styleSheet.cssText = cssCode;
			}
			else
			{
			    var cssCodeText = document.createTextNode(cssCode);
			    styleElmnt.appendChild(cssCodeText);
			}

			headElmnt.appendChild(styleElmnt);

			styleElmnt = null;
			headElmnt = null;
		},
		
		serialize: function(obj)
		{
			return null; // TODO
		}
	};
	
	phnq.util.Bitset.prototype =
	{
		set: function(idx, val)
		{
			var numIdx = Math.floor(idx / 24);
			var numElmnts = Math.max(numIdx - this.nums.length, 0);
			
			// zero should not expand the set
			if(!val && numElmnts > 0)
				return;
			
			while(numElmnts--)
				this.nums.push(0);
				
			var bit = idx % 24;
			
			if(val)
			{
				this.nums[numIdx] = this.nums[numIdx] | 1<<bit;
			}
			else
			{
				this.nums[numIdx] = this.nums[numIdx] & ~(1<<bit);
				
				while(this.nums.length && this.nums[this.nums.length-1] == 0)
					this.nums.pop();
			}
		},
		
		get: function(idx)
		{
			var numIdx = Math.floor(idx / 24);
			if(numIdx < this.nums.length)
			{
				var bit = idx % 24;
				
				return !!(this.nums[numIdx] & 1<<bit);
			}
			else
			{
				return false;
			}
		},
		
		toString: function()
		{
			return this.nums.join(",");
		}
	};


	phnq.util.URI.prototype =
	{
		init: function(uri, params)
		{
			if(!uri.match(/^https?:|^\//))
				uri = location.href.substring(0, location.href.lastIndexOf("/")+1) + uri;
			
			var re = /^((\w+):\/\/)?((\w+):?(\w+)?@)?([^\/\?:]+):?(\d+)?(\/?[^\?#]+)?\??([^#]+)?#?(\w*)/;                  
			var r = re.exec(uri);
			
			if(!r && typeof(location) != "undefined")
			{
				uri = location.protocol + "//" + location.host + uri;
				r = re.exec(uri);
			}

			if(!r)
				throw ("URIFormatExecption: "+uri);

			this.protocol = r[2];
			this.hostname = r[6];
			this.port = r[7];
			this.path = r[8];
			this.params = {};

			if(r[9])
			{
				var qsComps = r[9].split("&");
				for(var i=0; i<qsComps.length; i++)
				{
					var nvp = qsComps[i].split("=");
					this.params[nvp[0]] = unescape(nvp[1]);
				}
			}
			
			if(params)
			{
				for(var k in params)
				{
					this.params[k] = params[k];
				}
			}
		},
		
		getQueryString: function()
		{
			var nvps = [];
			for(k in this.params)
			{
				var val = this.params[k];
				if(val instanceof Array)
				{
					for(var i=0; i<val.length; i++)
					{
						nvps.push(k+"="+escape(val[i]));
					}
				}
				else
				{
					nvps.push(k+"="+escape(val));
				}
			}
			return nvps.join("&");
		},
		
		toString: function()
		{
			var buf = [];
			buf.push(this.protocol);
			buf.push("://");
			buf.push(this.hostname);
			if(this.port)
			{
				buf.push(":"+this.port);
			}
			if(this.path)
			{
				buf.push(this.path);
			}
			
			var qs = this.getQueryString();
			if(qs)
			{
				buf.push("?");
				buf.push(qs);
			}

			return buf.join("");
		}
	};
	
})();
